﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using System.Text;

namespace ILCodeExplorer
{
    public class ILInstruction
    {
        // Fields
        private OpCode code;
        private object operand;
        private byte[] operandData;
        private int offset;

        // Properties
        public OpCode Code
        {
            get { return code; }
            set { code = value; }
        }

        public object Operand
        {
            get { return operand; }
            set { operand = value; }
        }

        public byte[] OperandData
        {
            get { return operandData; }
            set { operandData = value; }
        }

        public int Offset
        {
            get { return offset; }
            set { offset = value; }
        }

        private bool avoidNamespace(string strNamespaceDescription)
        {
            foreach(string strAvoidedNamespace in Globals.NamespaceAvoidancePragmas)
            {
                if (strNamespaceDescription.StartsWith(strAvoidedNamespace))
                    return true;
            }
            return false;
        }

        /// <summary>
        /// Returns a friendly strign representation of this instruction
        /// </summary>
        /// <returns></returns>
        public string GetFunctionCallDescription()
        {
            string result = "";
            
            if (code.FlowControl == FlowControl.Call)
            {
                result += code;
                if (operand != null)
                {
                    switch (code.OperandType)
                    {
                        case OperandType.InlineField:
                            System.Reflection.FieldInfo fOperand = ((System.Reflection.FieldInfo) operand);
                            result += " " + Globals.ProcessSpecialTypes(fOperand.FieldType.ToString()) + " " +
                                      Globals.ProcessSpecialTypes(fOperand.ReflectedType.ToString()) +
                                      " " + fOperand.Name + "";
                            break;
                        case OperandType.InlineMethod:
                            try
                            {
                                System.Reflection.MethodInfo mOperand = (System.Reflection.MethodInfo) operand;

                                if (avoidNamespace(mOperand.ReflectedType.FullName))
                                    return "";

                                result += " ";
                                result += Globals.ProcessSpecialTypes(mOperand.ReturnType.ToString()) +
                                          " " + Globals.ProcessSpecialTypes(mOperand.ReflectedType.ToString()) +
                                          " " + mOperand.Name + "()";
                            }
                            catch
                            {
                                try
                                {
                                    System.Reflection.ConstructorInfo mOperand =
                                        (System.Reflection.ConstructorInfo) operand;

                                    // Calls to System classes are useless for us
                                    if (avoidNamespace(mOperand.ReflectedType.FullName))
                                        return "";

                                    result += " ";
                                    result += "void " +
                                              Globals.ProcessSpecialTypes(mOperand.ReflectedType.ToString()) +
                                              " " + mOperand.Name + "()";
                                }
                                catch
                                {
                                }
                            }
                            break;
                        case OperandType.ShortInlineBrTarget:
                        case OperandType.InlineBrTarget:
                            result += " " + GetExpandedOffset((int) operand);
                            break;
                        case OperandType.InlineType:
                            result += " " + Globals.ProcessSpecialTypes(operand.ToString());
                            break;
                        case OperandType.InlineString:
                            if (operand.ToString() == "\r\n") result += " \"\\r\\n\"";
                            else result += " \"" + operand.ToString() + "\"";
                            break;
                        case OperandType.ShortInlineVar:
                            result += operand.ToString();
                            break;
                        case OperandType.InlineI:
                        case OperandType.InlineI8:
                        case OperandType.InlineR:
                        case OperandType.ShortInlineI:
                        case OperandType.ShortInlineR:
                            result += operand.ToString();
                            break;
                        case OperandType.InlineTok:
                            if (operand is Type)
                                result += ((Type) operand).FullName;
                            else
                                result += "not supported";
                            break;

                        default:
                            result += "not supported";
                            break;
                    }
                }
            }
            return result;

        }


        /// <summary>
        /// Add enough zeros to a number as to be represented on 4 characters
        /// </summary>
        /// <param name="offset">
        /// The number that must be represented on 4 characters
        /// </param>
        /// <returns>
        /// </returns>
        private string GetExpandedOffset(long offset)
        {
            string result = offset.ToString();
            for (int i = 0; result.Length < 4; i++)
            {
                result = "0" + result;
            }
            return result;
        }

        public ILInstruction()
        {

        }
    }
}
